﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ImageProcessingLib
{
    /// <summary>
    /// Реализация классического алгоритма кластеризации KMeans
    /// </summary>
    class KMeans : IClustering
    {
        class MarkedVector
        {
            public Vector vector;
            public int label;
        }

        // массив центров кластеров
        Vector[] _centroids = null;

        // массив меток
        int[] _labels = null;

        public Vector[] Centroids => _centroids;

        public int[] Labels => _labels;

        public IMetric Metric { get; set; }

        public int MaxIterationCount { get; set; } = 100;

        #region === private ===

        /// <summary>
        /// Первоначальный выбор центров кластеров.
        /// </summary>
        /// <param name="observations">Массив векторов длдя кластеризации.</param>
        /// <param name="clusterCount">Количество кластеров.</param>
        void KInit(Vector[] observations, int clusterCount)
        {
            List<Vector> centroids = new List<Vector>();
            Random rnd = new Random();

            while (centroids.Count() < clusterCount)
            {
                int index = rnd.Next(observations.Length);

                if (!centroids.Any(x => x == observations[index]))
                {
                    centroids.Add(observations[index]);
                }                        
            }

            _centroids = centroids.ToArray();
        }

        /// <summary>
        /// Пересчитать положение центров кластеров.
        /// </summary>
        /// <param name="mvs">Коллекция MarkedVector.</param>
        void RecomputeCentroids(IEnumerable<MarkedVector> mvs)
        {
            var dim = mvs.ElementAt(0).vector.X.Length;

            for (int i = 0; i < _centroids.Length; i++)
            {
                var vectors = mvs.Where(x => x.label == i);

                for (int j = 0; j < dim; j++)
                {
                    _centroids[i].X[j] = vectors.Select(x => x.vector.X[j]).Average();
                }                
            }
        }

        #endregion

        public void Fit(Vector[] observations, int clusterCount)
        {
            KInit(observations, clusterCount);
            List<MarkedVector> mvs = observations.Select(v => new MarkedVector { vector = v, label = 0 }).ToList();
            bool move;
            int iteration = 0;

            while (true)
            {
                move = false;

                Parallel.ForEach(mvs, m =>
                {
                    int label = Predict(m.vector);

                    if (label != m.label)
                    {
                        m.label = label;
                        move = true;
                    }
                });

                if (!move)
                    break;

                if (MaxIterationCount > 0)
                {
                    iteration++;

                    if (iteration > MaxIterationCount)
                        break;
                }

                RecomputeCentroids(mvs);
            }

            _labels = mvs.Select(x => x.label).ToArray();
        }

        public int Predict(Vector vector)
        {
            float min = float.MaxValue;
            int index = -1;

            for (int i = 0; i < _centroids.Length; i++)
            {
                float d = Metric.Distance(_centroids[i], vector);

                if (d < min)
                {
                    min = d;
                    index = i;
                }
            }

            return index;
        }
    }
}
